/*meir grinstein*/
#define _GNU_SOURCE

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <dirent.h>

#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "ftp.h"
#include "gserver.h"
#include "tree.h"

#define FTP_FAIL    1
#define FTP_SUCCESS 0
#define OUT

typedef struct TTCell_t TTCell;
typedef struct SClient_t SClient;
typedef struct UserInfo_t UserInfo;
typedef struct Header_t Header;
typedef struct Socket_t Socket;

typedef int (*CmdFunc)(FtpServer *_ftp, SClient *_client, char *_param);
typedef int (*IncomeTypeFunc)(FtpServer *_ftp, SClient *_client, char *_bufffer, size_t _size);

typedef enum IncomeTypeState_t /* indicate state of incoming tramssnition type :file(binary) or command(string) */
{
    MTYPE_STATE_CMD = 0,
    MTYPE_STATE_FILE,
    MTYPE_STATE_NUM_OF_STATES
}FtpState;

typedef enum FtpCmd_t /* indicate the state of the commands of the ftp server*/
{
    FTP_CMD_ERROR = 0,
    FTP_CMD_USER,
    FTP_CMD_PASS,
    FTP_CMD_CD,
    FTP_CMD_LS,
    FTP_CMD_GET,
    FTP_CMD_PUT,
    FTP_CMD_BYE,
    FTP_CMD_NUM_OF_STATES
}FtpCmd;

char *ftpCmdStr[] = {"user", "pass", "cd", "ls", "get", "put", "bye"};

typedef enum CmdState_t
{
    CMD_STATE_ERROR = 0,
    CMD_STATE_WAIT_FOR_USER,
    CMD_STATE_WAIT_FOR_PASS1,
    CMD_STATE_WAIT_FOR_PASS2,
    CMD_STATE_WAIT_FOR_PASS3,
    CMD_STATE_LOGGED_IN,
    CMD_STATE_NUM_OF_STATES
}CmdState;

typedef enum CmdParam_t
{
    CMD_PARAM_ERROR = 0,
    CMD_PARAM_CMD,
    CMD_PARAM_RECV_FILE,
    CMD_PARAM_NUM_OF_STATES
}CmdParam;

typedef enum SocketType_t
{
    SOCKET_UNDEFINE = 0,
    SOCKET_TCP,
    SOCKET_UDP
}SocketType;

struct UserInfo_t
{
    char user[MAX_USER];
    char pass[MAX_PASS];
};

struct Header_t
{
    size_t size;
    size_t pos;
    int isLast;
    int err;
};

struct SClient_t
{
    int clientId;
    IncomeTypeFunc incomeFunc;
    CmdState cmdState;
    UserInfo *user;
    char cwd[MAX_PATH];
    Header lastHeader;
};

struct TTCell_t /*tansition table cell*/
{
    CmdFunc transitionFunc;
    int nextState;
    int nextStateOnFail;
};

struct FtpServer_t
{
    GServer *server;
    Tree *clients;
    Tree *users;
    TTCell ftpTT[CMD_STATE_NUM_OF_STATES][FTP_CMD_NUM_OF_STATES];
    IncomeTypeFunc incomeTypeFunc[MTYPE_STATE_NUM_OF_STATES];
};

typedef enum FtpReturnStr_t /* indicate the state of the commands of the ftp server*/
{
    RET_STR_ERROR = 0,
    RET_STR_USER_OK,
    RET_STR_USER_ERROR,
    RET_STR_PASS_OK,
    RET_STR_PASS_ERROR,
    RET_STR_CD_OK,
    RET_STR_CD_ERROR,
    RET_STR_LS_ERROR,
    RET_STR_GET_OK,
    RET_STR_GET_ERROR,
    RET_STR_PUT,
    RET_STR_BYE,
    RET_STR_NUM_OF_STATES
}FtpReturnStr;

char *ftpServerReturnStr[] = {"command error", "user ok", "user error", "pass ok", "pass error", "cd ok", "cd error", "ls error", "get ok", "get error", "put", "bye ok"};

/*client*/

typedef int (*ClientCmdFunc)(FtpClient *_client, const char *_buffer, const char *_param);

struct Socket_t
{
    SocketType socketType;
    int socketFd;
};

struct FtpClient_t
{
    ClientCmdFunc cmdFunc[FTP_CMD_NUM_OF_STATES];
    Socket socket;
};

/***************** FTP SERVER INTERNAL FUNCTION DECLARATION ***************/

static int SerClientLessFunc(void *_client1, void *_client2);
static int UserInfoLessFunc(void *_user1, void *_user2);

static int ProcessCmd(FtpServer *_ftp, SClient *_client, char *_bufffer, size_t _size);
static int ProcessFile(FtpServer *_ftp, SClient *_client, char *_bufffer, size_t _size);

static int CmdErrFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdUserFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdPassFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdCdFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdLsFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdGetFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdPutFunc(FtpServer *_ftp, SClient *_client, char *_param);
static int CmdByeFunc(FtpServer *_ftp, SClient *_client, char *_param);

static SClient* GetClient(Tree *_clients, int _clientId);
static int Parse(const char *_str, OUT FtpCmd *_cmd, OUT char* _param);
static int ServerSendString(FtpServer *_ftp, SClient *_client, const char *_str);
static UserInfo* FindUser(FtpServer *_ftp, const char *_user);
static int ReadUsersFromFile(FtpServer *_ftp, const char *_fileName);

/*callback functions for gserver*/
static size_t GetMessageSize  (void *_appData, void *_header, int _clientId);
static void   AddClient        (void *_appData, int _clientId);
static void   HandleMessage   (void *_appData, void *_inBuff, size_t _inBuffSize, int _clientId);
static void   DisconnectClient(void *_appData, size_t _inBuffSize, size_t _outBuffSize, int _clientId);

/*ftp client internal declaration*/
static int ClientCmdErrFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdUserFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdPassFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdCdFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdLsFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdGetFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdPutFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdByeFunc(FtpClient *_client, const char *_buffer, const char *_param);
static int ClientCmdRevcAndPrint(FtpClient *_client);

/* communication functions*/
static int CommAddHeader(OUT void *_buff, void *_data, size_t _dataSize);
static int ClientSendCmd(FtpClient *_client, const char *_buffer);
static int ClientRecv(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize,
               OUT Header *_header, int _maxSize);
static int ClientRecvFile(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize, OUT Header *_header);
static int ClientRecvCmd(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize, OUT Header *_header);

/************************* API FUNCTION IMPLEMETATION *************************/

FtpServer* FTPServerNew(int _port)
{
    FtpServer *newFtpServer;

    newFtpServer = (FtpServer*)malloc(sizeof(FtpServer));
    if(!newFtpServer)
    {
        fputs("malloc() failed to allocate FtpServer\n", stderr);
        return 0;
    }

    newFtpServer->clients = TreeNew(SerClientLessFunc);
    if (!newFtpServer->clients)
    {
        fputs("TreeNew() failed to make clients tree\n", stderr);
        goto undo_malloc_newFtpServer;
    }
    newFtpServer->users = TreeNew(UserInfoLessFunc);
    if (!newFtpServer->users)
    {
        fputs("TreeNew() failed to make users tree\n", stderr);
        goto undo_TreeNew_clients;
    }
    {
        TTCell initFtpTT[CMD_STATE_NUM_OF_STATES][FTP_CMD_NUM_OF_STATES] =
        {
                             /*FTP_CMD_ERROR                                FTP_CMD_USER                                                   FTP_CMD_PASS                                               FTP_CMD_CD                                          FTP_CMD_LS                                          FTP_CMD_GET                                          FTP_CMD_PUT                                          FTP_CMD_BYE*/
/*CMD_STATE_ERROR*/          {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},                  {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},              {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR}},
/*CMD_STATE_WAIT_FOR_USER*/  {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdUserFunc,CMD_STATE_WAIT_FOR_PASS1,CMD_STATE_WAIT_FOR_USER},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},              {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR}},
/*CMD_STATE_WAIT_FOR_PASS1*/ {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},                  {CmdPassFunc,CMD_STATE_LOGGED_IN,CMD_STATE_WAIT_FOR_PASS2},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR}},
/*CMD_STATE_WAIT_FOR_PASS2*/ {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},                  {CmdPassFunc,CMD_STATE_LOGGED_IN,CMD_STATE_WAIT_FOR_PASS3},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR}},
/*CMD_STATE_WAIT_FOR_PASS3*/ {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},                  {CmdPassFunc,CMD_STATE_LOGGED_IN,CMD_STATE_WAIT_FOR_USER}, {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},       {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},        {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR}},
/*CMD_STATE_LOGGED_IN*/      {{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},{CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},                  {CmdErrFunc,CMD_STATE_ERROR,CMD_STATE_ERROR},              {CmdCdFunc,CMD_STATE_LOGGED_IN,CMD_STATE_LOGGED_IN},{CmdLsFunc,CMD_STATE_LOGGED_IN,CMD_STATE_LOGGED_IN},{CmdGetFunc,CMD_STATE_LOGGED_IN,CMD_STATE_LOGGED_IN},{CmdPutFunc,CMD_STATE_LOGGED_IN,CMD_STATE_LOGGED_IN},{CmdByeFunc,CMD_STATE_WAIT_FOR_USER,CMD_STATE_LOGGED_IN}}
        };
        memcpy(newFtpServer->ftpTT, initFtpTT, sizeof(newFtpServer->ftpTT));
    }
    {
        IncomeTypeFunc incomeTypeFunc[MTYPE_STATE_NUM_OF_STATES] = {ProcessCmd, ProcessFile};
        memcpy(newFtpServer->incomeTypeFunc, incomeTypeFunc, sizeof(newFtpServer->incomeTypeFunc));
    }
    newFtpServer->server = GServerNew(_port, newFtpServer, sizeof(Header), GetMessageSize, AddClient,
                                      HandleMessage, 0, 0/*NewException*/, DisconnectClient);
    if (!newFtpServer->server)
    {
        fputs("TreeNew() failed to make users tree\n", stderr);
        goto undo_TreeNew_users;
    }

    if (FTP_FAIL == ReadUsersFromFile(newFtpServer, USERS_FILE))
    {
        fputs("ReadUsersFromFile() failed\n", stderr);
    }

    if (-1 == mkdir(FTP_PATH, 0777) && errno != EEXIST)
    {
        perror("mkdir() failed");
        goto undo_GServerNew;
    }
    if (-1 == chdir(FTP_PATH))
    {
        perror("cddir() failed");
        goto undo_GServerNew;
    }
    
    return newFtpServer;

    undo_GServerNew:
    GServerDelete(newFtpServer->server);
    undo_TreeNew_clients:
    TreeDel(newFtpServer->clients);
    undo_TreeNew_users:
    TreeDel(newFtpServer->users);
    undo_malloc_newFtpServer:
    free(newFtpServer);

    return 0;
}
void FTPServerDel(FtpServer *_this)
{
    TreeDel(_this->clients);
    TreeDel(_this->users);
    GServerDelete(_this->server);
    free(_this);
}

int FTPServerAddUser(FtpServer *_this, const char *_user, const char *_pass)
{
    UserInfo *newUser = (UserInfo*)malloc(sizeof(UserInfo));
    strcpy (newUser->user, _user);
    strcpy (newUser->pass, _pass);
    
    TreeInsert(_this->users, newUser);
    return 0;
}

int FTPServerRun(FtpServer *_this)
{
    GServerRun(_this->server);
    return 0;
}

/*client api implementation*/
FtpClient* FTPClientNew(void)
{
    FtpClient *newClient = (FtpClient*)malloc (sizeof(FtpClient));
    if (!newClient)
    {
        fputs("malloc() failed to make FtpClient\n", stderr);
        return 0;
    }
    newClient->socket.socketType = SOCKET_TCP;
    {
        ClientCmdFunc cmdFunc[FTP_CMD_NUM_OF_STATES] = {ClientCmdErrFunc, ClientCmdUserFunc, ClientCmdPassFunc, ClientCmdCdFunc, ClientCmdLsFunc, ClientCmdGetFunc, ClientCmdPutFunc, ClientCmdByeFunc};
        memcpy(newClient->cmdFunc, cmdFunc, sizeof(newClient->cmdFunc));
    }
    return newClient;
}

int FTPClientConnect(FtpClient *_this, const char *_ip, int port)
{
    int conn_good = 0;
    struct sockaddr_in server_addr;

    _this->socket.socketFd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == _this->socket.socketFd)
    {
        perror("client socket fail");
        exit(1);
    }

    /* Set structure */
    memset((char *) &server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = port;
    server_addr.sin_addr.s_addr = inet_addr(_ip);/*converts the Internet host address cp from  the  IPv4  num‐
                                                  bers-and-dots  notation  into  binary  form (in network byte order)*/

    while (!conn_good)
    {
        if (-1 == connect(_this->socket.socketFd, (struct sockaddr *)&server_addr, sizeof(server_addr)))
        {
            perror("client connect fail (try again)");
            usleep(100000);
        }
        else
        {
            conn_good = 1;
        }
    }
    return FTP_SUCCESS;
}

void FTPClientDel(FtpClient *_this)
{
    close(_this->socket.socketFd);
    free(_this);
}

int FTPClientExec(FtpClient *_this, const char *_cmd)
{
    FtpCmd cmd;
    char param[MAX_COMMAND_LINE];
    
    Parse(_cmd, &cmd, (char*)&param);
    
printf("client send command : \"%s\"\n", _cmd);

    if (ClientSendCmd(_this, _cmd) == FTP_FAIL)
    {
        fputs("ClientSendCmd() failed\n", stderr);
        return FTP_FAIL;
    }

    _this->cmdFunc[cmd](_this, _cmd, param);

    return 0;
}

/***************** FTP SERVER INTERNAL FUNCTION IMPLEMENTATION ***************/

int SerClientLessFunc(void *_client1, void *_client2)
{
    return ((SClient*)_client1)->clientId < ((SClient*)_client2)->clientId;
}

int UserInfoLessFunc(void *_user1, void *_user2)
{
    return (strcmp(((UserInfo*)_user1)->user,((UserInfo*)_user2)->user) < 0);
}

int ProcessCmd(FtpServer *_ftp, SClient *_client, char *_bufffer, size_t _size)
{
    FtpCmd cmd;
    int ret;
    char param[MAX_COMMAND_LINE];
    
printf("srever recieve command : \"%s\"\n", _bufffer);

    Parse(_bufffer, &cmd, (char*)&param);

    ret = _ftp->ftpTT[_client->cmdState][cmd].transitionFunc(_ftp, _client, param);
    
    _client->cmdState = !ret ? _ftp->ftpTT[_client->cmdState][cmd].nextState :
                               _ftp->ftpTT[_client->cmdState][cmd].nextStateOnFail;
   
    return MTYPE_STATE_CMD;
}
int ProcessFile(FtpServer *_ftp, SClient *_client, char *_bufffer, size_t _size)
{
    return MTYPE_STATE_FILE;
}

int CmdErrFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_ERROR]))
    {
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int CmdUserFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    _client->user = FindUser(_ftp, _param);
    if (!_client->user)
    {
        if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_USER_ERROR]))
        {
            fputs("ServerSendString() failed\n", stderr);
        }
        return FTP_FAIL;
    }

    if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_USER_OK]))
    {
        fputs("ServerSendString() failed\n", stderr);
        return FTP_FAIL;
    }

    return FTP_SUCCESS;
}

int CmdPassFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    if (strcmp(_param, _client->user->pass))
    {
        if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_PASS_ERROR]))
        {
            fputs("ServerSendString() failed\n", stderr);
        }
        return FTP_FAIL;
    }
    if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_PASS_OK]))
    {
        fputs("ServerSendString() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int CmdCdFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    struct stat st;

    if (strlen(_param) > MAX_PATH)
    {
        fputs("CmdCdFunc() failed, _param is too long\n", stderr);
        if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_CD_ERROR]))
        {
            fputs("ServerSendString() failed", stderr);
        }
        return FTP_FAIL;
    }
    if (strstr(_param, ".."))
    {
        fputs("CmdCdFunc() failed, _param contains \"..\"\n", stderr);
        if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_CD_ERROR]))
        {
            fputs("ServerSendString() failed\n", stderr);
        }
        return FTP_FAIL;
    }
    if(stat(_param,&st) != 0)
    {
        if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_CD_ERROR]))
        {
            fputs("ServerSendString() failed\n", stderr);
        }
        return FTP_FAIL;
    }

    strcpy (_client->cwd, _param);
    
    if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_CD_OK]))
    {
        fputs("ServerSendString() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int CmdLsFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    char buff[MAX_PACKET_SIZE];
    int len = 1;
    struct dirent *dp;
    DIR *dirp;

    buff[0] = '\0';
    dirp=opendir(_client->cwd);
    while((dp=readdir(dirp)))
    {
        len += strlen(dp->d_name);
        if (len > MAX_PACKET_SIZE)
        {
            fputs("ls size is too big\n", stderr);
            break;
        }
        strcat(buff, "\n");
        strcat(buff, dp->d_name);
    }

    if (FTP_FAIL == ServerSendString(_ftp, _client, buff))
    {
        fputs("ServerSendString() failed\n", stderr);
        return FTP_FAIL;
    }

/*    int ret, status;
    pid_t pid, w;

    char *pathvar;
    char newpath[1000];

    pathvar = getenv("PWD");
    strcpy(newpath, pathvar);
    strcat(newpath, ":u/userid/bin");
    setenv("PATH", newpath, 1);

    if ((pid = fork()) == -1)
    {
        perror("fork error");
    }
    else if (pid == 0)
    {
        execlp("ls", "ls", 0);
        ret = execlp("cd", "cd", "nbproject");
        execlp("ls", "ls", 0);
        exit(ret);
    }
        do {
            w = waitpid(pid, &status, WUNTRACED | WCONTINUED);
            if (w == -1) { perror("waitpid"); exit(EXIT_FAILURE); }
            if (WIFEXITED(status)) {
                printf("exited, status=%d\n", WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("killed by signal %d\n", WTERMSIG(status));
            } else if (WIFSTOPPED(status)) {
                printf("stopped by signal %d\n", WSTOPSIG(status));
            } else if (WIFCONTINUED(status)) {
                printf("continued\n");
            }
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));

    ret = errno;*/


    return FTP_SUCCESS;
}
int CmdGetFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    char buff[MAX_PACKET_SIZE];
    char fileName[MAX_FILE_NAME];
    FILE *file = 0;
    Header header;

    strcpy(fileName, _client->cwd);
    strcat(fileName, "/");
    strcat(fileName, _param);
    file = fopen(fileName, "r");
    if (!file)
    {
        if (FTP_FAIL == ServerSendString(_ftp, _client, strerror(errno)))
        {
            fputs("ServerSendString() failed\n", stderr);
        }
        return FTP_FAIL;
    }
    header.pos=0;
    while (!feof(file))
    {
        header.size = fread(buff+sizeof(Header), 1, MAX_PACKET_SIZE-sizeof(Header), file);
        if (header.size < 1)
        {
            perror("fread() failed");
            if (FTP_FAIL == ServerSendString(_ftp, _client, "error while reading file"))
            {
                fputs("ServerSendString() failed\n", stderr);
            }
            return FTP_FAIL;
        }
        header.isLast = feof(file)?1:0;
        memcpy(buff, &header, sizeof(Header));
        if (GServerWrite(_ftp->server, _client->clientId, buff, header.size+sizeof(Header))!=0)
        {
            fputs("GServerWrite() failed\n", stderr);
            return FTP_FAIL;
        }
        header.pos += header.size;
    }
    if (FTP_FAIL == ServerSendString(_ftp, _client, "Finished get ok"))
    {
        fputs("ServerSendString() failed\n", stderr);
    }

    return FTP_SUCCESS;
}
int CmdPutFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    return FTP_SUCCESS;
}
int CmdByeFunc(FtpServer *_ftp, SClient *_client, char *_param)
{
    if (FTP_FAIL == ServerSendString(_ftp, _client, ftpServerReturnStr[RET_STR_BYE]))
    {
        fputs("ServerSendString() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

SClient* GetClient(Tree *_clients, int _clientId)
{
    SClient fakeClient;
    TreeItr itr;
    fakeClient.clientId = _clientId;
    itr = TreeFind(_clients, &fakeClient);
    if (TreeItrIsEqual(itr, TreeEnd(_clients)))
        return 0;
    return ((SClient*)TreeGetData(itr));
}

int Parse(const char *_str, OUT FtpCmd *_cmd, OUT char *_param)
{
    char cmd[MAX_COMMAND_LINE];
    char *space;
    int i;
    
    assert (_str);
    assert (_cmd);
    assert (_param);

    space = strchr(_str, ' ');

    if (space)
    {
        strcpy(_param, space+1);
        strncpy(cmd, _str, space-_str);
        cmd[space-_str] = '\0';
    }
    else
    {
       strcpy(cmd, _str);
    }

    for (i=0; i<FTP_CMD_NUM_OF_STATES-1; ++i)
    {
        if (!strcmp(cmd, ftpCmdStr[i]))
        {
            *_cmd = (FtpCmd)(i+1);
            return 0;
        }
    }

    *_cmd = (FtpCmd)0;
    return 0;
}

UserInfo* FindUser(FtpServer *_ftp, const char *_user)
{
    UserInfo inUser;
    TreeItr itr;
    strcpy(inUser.user, _user);
    itr = TreeFind(_ftp->users, &inUser);
    if (TreeItrIsEqual(itr, TreeEnd(_ftp->users)))
    {
        return 0;
    }
    return (UserInfo*)TreeGetData(itr);
}

int ReadUsersFromFile(FtpServer *_ftp, const char *_fileName)
{
    FILE *file = fopen(_fileName, "r");
    char user[MAX_USER], pass[MAX_PASS];

    if (!file)
    {
        perror("can't find users file");
        return FTP_FAIL;
    }

    while (1)
    {
        fgets(user , MAX_USER, file);
        if (feof(file))
        {
            break;
        }
        fgets(pass , MAX_PASS, file);
        if (FTP_FAIL == FTPServerAddUser(_ftp, user, pass))
        {
            fputs("FTPServerAddUser() failed", stderr);
            fclose(file);
            return FTP_FAIL;
        }
    }
    
    fclose(file);
    return FTP_SUCCESS;
}

/*callback functions for gserver*/
size_t GetMessageSize(void *_appData, void *_header, int _clientId)
{
    FtpServer *ftp = 0;
    SClient *client = 0;

    if (_clientId && _appData)
    {
        ftp = (FtpServer*)_appData;
        client = GetClient(ftp->clients, _clientId);
        if (!client)
        {
            fputs("GetClient() failed fo find clientId\n", stderr);
            return 0;
        }
        memcpy (&client->lastHeader ,_header ,sizeof(Header));
    }
   
    return ((Header*)_header)->size;
}
void AddClient(void *_appData, int _clientId)
{
    FtpServer *ftp = (FtpServer*)_appData;
    SClient *newClient = (SClient*)malloc(sizeof(SClient));
    if(!newClient)
    {
        fputs("malloc() failed to allocate SClient\n", stderr);
        return;
    }

    newClient->clientId = _clientId;
    newClient->cmdState = CMD_STATE_WAIT_FOR_USER;
    strcpy(newClient->cwd, ".");
    newClient->user = 0;
    newClient->incomeFunc = ProcessCmd;

    TreeInsert(ftp->clients, newClient);
}
void HandleMessage(void *_appData, void *_inBuff, size_t _inBuffSize, int _clientId)
{
    FtpServer *ftp = (FtpServer*)_appData;
    SClient *client = GetClient(ftp->clients, _clientId);
    if (!client)
    {
        fputs("GetClient() failed fo find clientId\n", stderr);
        return;
    }

    client->incomeFunc = ftp->incomeTypeFunc[client->incomeFunc(ftp, client, _inBuff, _inBuffSize)];
}
void DisconnectClient(void *_appData, size_t _inBuffSize, size_t _outBuffSize, int _clientId)
{
    FtpServer *ftp = (FtpServer*)_appData;
    SClient *client = 0;
    SClient fakeClient;
    TreeItr itr;
    
    fakeClient.clientId = _clientId;
    itr = TreeFind(ftp->clients, &fakeClient);
    if (TreeItrIsEqual(itr, TreeEnd(ftp->clients)))
        return;
    client = (SClient*)TreeGetData(itr);

    TreeRemove(ftp->clients, itr);
    
printf("client %d disconnected callback\n", _clientId);
}

/*client functions*/
int ClientCmdErrFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    return FTP_SUCCESS;
}
int ClientCmdUserFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int ClientCmdPassFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int ClientCmdCdFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int ClientCmdLsFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int ClientCmdGetFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    char fileBuff[MAX_PACKET_SIZE];
    Header header;
    int ret;
    size_t buffSize;
    FILE *file = 0;

    file = fopen(_param, "w");
    if (!file)
    {
        fputs("fopen() failed\n", stderr);
        return FTP_FAIL;
    }

    header.isLast=0;
    while(!header.isLast)
    {
        if (FTP_FAIL == ClientRecvFile(_client, fileBuff, &buffSize, &header))
        {
            fputs("ClientRecvFile() failed\n", stderr);
            fclose(file);
            return FTP_FAIL;
        }
        fseek(file, header.pos, SEEK_SET);
        ret = fwrite(fileBuff, 1, header.size, file);
        if (-1 == ret)
        {
            perror("fwrite() failed");
            fclose(file);
            return FTP_FAIL;
        }
    }
    fclose(file);
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int ClientCmdPutFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}
int ClientCmdByeFunc(FtpClient *_client, const char *_buffer, const char *_param)
{
    if (FTP_FAIL == ClientCmdRevcAndPrint(_client))
    {
        fputs("ClientCmdRevcAndPrint() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int ClientCmdRevcAndPrint(FtpClient *_client)
{
    size_t buffSize;
    Header header;
    char buff[MAX_COMMAND_LINE+sizeof(Header)];

    if (FTP_FAIL == ClientRecvCmd(_client, (void*)buff, &buffSize, &header))
    {
        fputs("ClientRecvCmd() failed\n", stderr);
        return FTP_FAIL;
    }

    printf("client recieve: \"%s\"\n", buff);
    return FTP_SUCCESS;
}

/* communication functions*/
int CommAddHeader(OUT void *_buff, void *_data, size_t _dataSize)
{
    Header header;
    header.size = _dataSize;
    memcpy (_buff, &header, sizeof(Header));
    memcpy ((char*)_buff+sizeof(Header), _data, _dataSize);

    return FTP_SUCCESS;
}

int ClientSendCmd(FtpClient *_client, const char *_buffer)
{
    int ret;

    char *buff = 0;

    size_t buffSize = strlen(_buffer)+1+sizeof(Header);
    buff = (char*)malloc(buffSize);
    if (!buff)
    {
        fputs("malloc() failed\n", stderr);
        return FTP_FAIL;
    }
    CommAddHeader(buff, (void*)_buffer, strlen(_buffer)+1);

    ret = send(_client->socket.socketFd, buff, buffSize, 0);
    if (ret < 1)
    {
        perror("send() failed");
        return FTP_FAIL;
    }
    if (ret != buffSize)
    {
        fputs("send() failed\n", stderr);
        return FTP_FAIL;
    }

    free(buff);
    return FTP_SUCCESS;
}

int ClientRecvCmd(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize, OUT Header *_header)
{
    if(FTP_FAIL == ClientRecv(_client, _buffer, _buffSize, _header, MAX_COMMAND_LINE))
    {
        fputs("ClientRecv() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int ClientRecvFile(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize, OUT Header *_header)
{
    if(FTP_FAIL == ClientRecv(_client, _buffer, _buffSize, _header,
                              MAX_PACKET_SIZE-sizeof(Header)))
    {
        fputs("ClientRecv() failed\n", stderr);
        return FTP_FAIL;
    }
    return FTP_SUCCESS;
}

int ClientRecv(FtpClient *_client,OUT char *_buffer,OUT size_t *_buffSize,
               OUT Header *_header, int _maxSize)
{
    int ret;
    Header header;

    ret = recv(_client->socket.socketFd, &header, sizeof(Header), 0);
    if (ret < 1)
    {
        perror("send(header) in ClientRecv failed");
        return FTP_FAIL;
    }
    if (ret != sizeof(Header))
    {
        fputs("send(header) in ClientRecv failed\n", stderr);
        return FTP_FAIL;
    }

    if (_header)
    {
        *_header = header;
    }

    *_buffSize = GetMessageSize(0, (void*)&header, 0);
    if (*_buffSize > _maxSize)
    {
        fprintf(stderr, "client got command biger then %d\n", _maxSize);
        return FTP_FAIL;
    }

    ret = recv(_client->socket.socketFd, _buffer, *_buffSize, 0);
    if (ret < 1)
    {
        perror("send(data) in ClientRecv failed");
        return FTP_FAIL;
    }
    if (ret != *_buffSize)
    {
        fputs("send(data) in ClientRecv failed\n", stderr);
        return FTP_FAIL;
    }

    return FTP_SUCCESS;
}

int ServerSendString(FtpServer *_ftp, SClient *_client, const char *_str)
{
    char buff[MAX_COMMAND_LINE+sizeof(Header)];

    CommAddHeader(buff, (void*)_str, strlen(_str)+1);
    if (GServerWrite(_ftp->server, _client->clientId, buff, strlen(_str)+1+sizeof(Header))!=0)
    {
        fputs("GServerWrite() failed\n", stderr);
        return FTP_FAIL;
    }

    return FTP_SUCCESS;
}